// <copyright file="ScreenCell.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>April 2009</date>
// <summary>Display cell.</summary>

// License:

// Product: Ascii / GDI+ Graphics Manager. Library for RPGRoguelikeEngine .Net.
// Author: Robert N. Tenney
// Copyright: April 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;

using TenneySoftware.Engine;

namespace TenneySoftware.GraphicsManagers {
    /// <summary>
    /// ScreenCell class holds a display cell as well as methods for generating the cells.
    /// </summary>
    public class ScreenCell : IDisposable {
        #region Fields:
        /// <summary>
        /// Static square size of the cell in tiles.
        /// </summary>
        private static int cellSize = 10;

        /// <summary>
        /// Static manager that is using this class.
        /// </summary>
        private static SimpleTileGdi manager;

        /// <summary>
        /// Bitmap to hold the cell's level image.
        /// </summary>
        private Bitmap levelImage;

        /// <summary>
        /// Bitmap to hold the cell's map image.
        /// </summary>
        private Bitmap mapImage;

        /// <summary>
        /// Graphics object to draw the cell's level image.
        /// </summary>
        private Graphics levelGraphics;

        /// <summary>
        /// Graphics object to draw the cell's map image.
        /// </summary>
        private Graphics mapGraphics;

        /// <summary>
        /// Rectangular region of the cell.
        /// </summary>
        private Rectangle cellRectangle;

        /// <summary>
        /// Rectangular region of the cell as it is in the display's level image.
        /// </summary>
        private Rectangle cellDisplayRectangleLevel;

        /// <summary>
        /// Rectangular region of the cell as it is in the display's map image.
        /// </summary>
        private Rectangle cellDisplayRectangleMap;

        /// <summary>
        /// Flag to indicate whether this cell has changed since the last update.
        /// </summary>
        private bool unchanged = true;

        /// <summary>
        /// Global: Current tile's data.
        /// </summary>
        private TileData currentTile;
        #endregion

        #region Constructors:
        /// <summary>
        /// Initializes a new instance of the ScreenCell class.
        /// </summary>
        public ScreenCell() {
        }
        #endregion

        #region Properties:
        /// <summary>
        /// Gets or sets a static variable to hold the manager that is using the screen cell class.
        /// </summary>
        /// <value>
        /// The manager that is using the screen cell class.
        /// </value>
        public static SimpleTileGdi Manager {
            get {
                return ScreenCell.manager;
            }

            set {
                ScreenCell.manager = value;
            }
        }

        /// <summary>
        /// Gets or sets the static value for the size of the cells. Default is 10.
        /// </summary>
        /// <value>
        /// The value for the size of the cells. Default is 10.
        /// </value>
        public static int CellSize {
            get {
                return ScreenCell.cellSize;
            }

            set {
                ScreenCell.cellSize = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether there where changes made to this cell or not.
        /// </summary>
        /// <value>
        /// Flag indicating whether there where changes made to this cell or not.
        /// </value>
        public bool Unchanged {
            get {
                return this.unchanged;
            }
        }

        /// <summary>
        /// Gets or sets the rectangular bounds for this cell.
        /// </summary>
        /// <value>
        /// The rectangular bounds for this cell.
        /// </value>
        public Rectangle CellRectangle {
            get {
                return this.cellRectangle;
            }

            set {
                this.cellRectangle = value;
                this.levelImage = new Bitmap(manager.TileSizeLevel.Width * value.Width, manager.TileSizeLevel.Height * value.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                this.levelGraphics = Graphics.FromImage(this.levelImage);
                this.mapImage = new Bitmap(manager.TileSizeMap.Width * value.Width, manager.TileSizeMap.Height * value.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                this.mapGraphics = Graphics.FromImage(this.mapImage);

                this.levelGraphics.CompositingQuality = CompositingQuality.HighSpeed;
                this.levelGraphics.InterpolationMode = InterpolationMode.Low;
                this.levelGraphics.SmoothingMode = SmoothingMode.None;
                this.levelGraphics.PixelOffsetMode = PixelOffsetMode.None;
                this.levelGraphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixel;

                this.mapGraphics.CompositingQuality = CompositingQuality.HighSpeed;
                this.mapGraphics.InterpolationMode = InterpolationMode.Low;
                this.mapGraphics.SmoothingMode = SmoothingMode.None;
                this.mapGraphics.PixelOffsetMode = PixelOffsetMode.None;
                this.mapGraphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixel;

                int wid = manager.TileSizeLevel.Width;
                int hei = manager.TileSizeLevel.Height;
                this.cellDisplayRectangleLevel = new Rectangle(
                    value.X * wid,
                    value.Y * hei,
                    value.Width * wid,
                    value.Height * hei);
                wid = manager.TileSizeMap.Width;
                hei = manager.TileSizeMap.Height;
                this.cellDisplayRectangleMap = new Rectangle(
                    value.X * wid,
                    value.Y * hei,
                    value.Width * wid,
                    value.Height * hei);
            }
        }

        /// <summary>
        /// Gets the rectangle where the current cell is located on the level display.
        /// </summary>
        /// <value>
        /// The rectangle where the current cell is located on the level display.
        /// </value>
        public Rectangle CellDisplayRectangleLevel {
            get {
                return this.cellDisplayRectangleLevel;
            }
        }

        /// <summary>
        /// Gets the rectangle where the current cell is locate on the map display.
        /// </summary>
        /// <value>
        /// The rectangle where the current cell is locate on the map display.
        /// </value>
        public Rectangle CellDisplayRectangleMap {
            get {
                return this.cellDisplayRectangleMap;
            }
        }

        /// <summary>
        /// Gets the level image for this cell.
        /// </summary>
        /// <value>
        /// The level image for this cell.
        /// </value>
        public Bitmap GetLevelImage {
            get {
                return this.levelImage;
            }
        }

        /// <summary>
        /// Gets the map image for this cell.
        /// </summary>
        /// <value>
        /// The map image for this cell.
        /// </value>
        public Bitmap GetMapImage {
            get {
                return this.mapImage;
            }
        }
        #endregion

        #region Public Methods:
        /// <summary>
        /// A static method to generate all of the cells needed for the level.
        /// </summary>
        /// <returns>A ScreenCell jagged array of the cells for this level.</returns>
        public static ScreenCell[][] GenerateCells() {
            int width = manager.Engine.Level.Width;
            int height = manager.Engine.Level.Height;

            int widthCount = (width / cellSize) + (width % cellSize > 0 ? 1 : 0);
            int heightCount = (height / cellSize) + (height % cellSize > 0 ? 1 : 0);

            ScreenCell[][] temp = new ScreenCell[heightCount][];
            int offsetY = 0;
            for (int line = 0; line < heightCount; line++) {
                int offsetX = 0;
                temp[line] = new ScreenCell[widthCount];
                for (int column = 0; column < widthCount; column++) {
                    temp[line][column] = new ScreenCell();
                    temp[line][column].CellRectangle = new Rectangle(offsetX, offsetY, cellSize, cellSize);
                    offsetX += cellSize;
                }

                offsetY += cellSize;
            }

            return temp;
        }

        /// <summary>
        /// Generates the level and map images for this cell.
        /// </summary>
        public void GenerateImageCell() {
            Point offsetTile = Point.Empty;
            Point offsetMap = Point.Empty;

            TileImages found;

            // Check to see if FOV was just toggled
            if (manager.ToggledFieldOfView) {
                // If so, clear the screen and redraw everything.
                this.levelGraphics.Clear(Color.Black);
                this.mapGraphics.Clear(Color.Black);
            }

            // Iterate through the whole level.
            this.unchanged = true;
            for (int y = this.cellRectangle.Top; y < Math.Min(this.cellRectangle.Bottom, ScreenCell.manager.Engine.Level.Height); y++) {
                for (int x = this.cellRectangle.Left; x < Math.Min(this.cellRectangle.Right, ScreenCell.manager.Engine.Level.Width); x++) {
                    // Get the tile at the current location.
                    try {
                        this.currentTile = ScreenCell.manager.Engine.Level.GetLevel(x, y);
                    } catch (IndexOutOfRangeException) {
                        continue;
                    }

                    // Check to see if: We are in FOV, if not, check to see if the tile had an object that was changed since
                    // the last draw, and if so, check to see if FOV was toggled recently....
                    if (!this.currentTile.TileStateChanged  && !ScreenCell.manager.DoFieldOfView && !ScreenCell.manager.ToggledFieldOfView) {
                        // If all of those are false, then we don't want to redraw the tile this turn.
                        continue;
                    } else {
                        // If any are true, then there might be something to draw here, and since there may be a draw, then
                        // acknowledge the object change, and reset....continue with the other checks now...
                        this.currentTile.TileStateChanged = false;
                    }

                    // Check to see if two conditions are true:
                    // 1 - Was the tile not seen(therefore should be blank) AND are we in FOV,
                    // 2 - Did the tile not change since the last draw AND there was no toggle with FOV AND there are NO
                    //      changes image since last draw.
                    if ((!this.currentTile.HasSeen && ScreenCell.manager.DoFieldOfView) ||
                            (!this.currentTile.TileChanged && !ScreenCell.manager.ToggledFieldOfView && !ScreenCell.manager.Changed)) {
                        // If either of those conditions are true, this tile does NOT need to be redrawn.
                        continue;
                    }

                    // If one of those conditions are true, as well as the prior check, then the tile needs to be redrawn.
                    // Find the tile to draw...
                    found = ScreenCell.manager.DrawTiles.Find(this.currentTile.TileDescription);

                    // If the tile was marked as changed, then reset it, since it was now updated.
                    this.currentTile.TileChanged = false;

                    // Set the location to draw this tile on the buffered image.
                    offsetTile.Offset(
                        (x * ScreenCell.manager.TileSizeLevel.Width) - (this.cellRectangle.X * ScreenCell.manager.TileSizeLevel.Width),
                        (y * ScreenCell.manager.TileSizeLevel.Height) - (this.cellRectangle.Y * ScreenCell.manager.TileSizeLevel.Height));
                    offsetMap.Offset(
                        (x * ScreenCell.manager.TileSizeMap.Width) - (this.cellRectangle.X * ScreenCell.manager.TileSizeMap.Width),
                        (y * ScreenCell.manager.TileSizeMap.Height) - (this.cellRectangle.Y * ScreenCell.manager.TileSizeMap.Height));

                    // Draw the tile in the correct image.
                    this.unchanged = false;
                    this.levelGraphics.DrawImageUnscaled(((!this.currentTile.CanSee && ScreenCell.manager.DoFieldOfView) ? found.LevelImageShadow : found.LevelImage), offsetTile);
                    if (this.currentTile.IsPlayerHere) {
                        this.levelGraphics.DrawImageUnscaled(((!this.currentTile.CanSee && ScreenCell.manager.DoFieldOfView) ? ScreenCell.manager.PlayerImage.LevelImageShadow : ScreenCell.manager.PlayerImage.LevelImage), offsetTile);
                        this.mapGraphics.DrawImageUnscaled(((!this.currentTile.CanSee && ScreenCell.manager.DoFieldOfView) ? ScreenCell.manager.PlayerImage.MapImageShadow : ScreenCell.manager.PlayerImage.MapImage), offsetMap);
                    } else {
                        this.mapGraphics.DrawImageUnscaled(((!this.currentTile.CanSee && ScreenCell.manager.DoFieldOfView) ? found.MapImageShadow : found.MapImage), offsetMap);
                    }

                    // Reset offset point.
                    offsetMap = Point.Empty;
                    offsetTile = Point.Empty;
                }
            }
        }

        /// <summary>
        /// Disposethis class instance.
        /// </summary>
        public void Dispose() {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Protected Methods:
        /// <summary>
        /// Dispose this class instance.
        /// </summary>
        /// <param name="disposing">Dispose managed resources.</param>
        protected virtual void Dispose(bool disposing) {
            if (disposing) {
                // dispose managed resources
                this.levelImage.Dispose();
                this.levelGraphics.Dispose();
                this.mapImage.Dispose();
                this.mapGraphics.Dispose();
            }

            // free native resources
        }
        #endregion
    }
}
